import java.util.ArrayList;
import java.util.Stack;


/**
 * Cette classe effectue le calcul d'op�rations arithm�tiques.
 * Elle r�alise aussi la v�rification des types avant ces calculs. En cas de conflit de type, l'ex�cution continue mais un message d'erreur s'affichera � la fin.
 * 
 * @author Guillaume PANNETIER
 * @author Bertrand LE MAROUILLE
 * @author Quentin DORVEAUX
 * @author Corentin GUICHAOUA
 *
 */
public class Expression {

	private Stack<Integer> pileType;
	private Stack<String> pileOp;
	private Stack<IdFun> pileFun;
	private Stack<Integer> nbArg;

	/**
	 * Constructeur de la classe. Il instancie les diff�rentes piles � utiliser.
	 */
	public Expression(){
		pileType= new Stack<Integer>();
		pileOp= new Stack<String>();
		pileFun=new Stack<IdFun>();
		nbArg = new Stack<Integer>();
	}


	/**
	 * Empile un type dans la pile des types.
	 * @param type Type � empiler.
	 */
	public void empileType(int type){
		pileType.push(type);
	}

	/**
	 * D�pile le dernier type de la pile des types et le renvoie.
	 * @return Type anciennement en sommet de pile.
	 */
	public int depileType(){
		return pileType.pop();
	}

	/**
	 * Empile dans la pile des types le type de l'ident dont le nom est pass� en param�tre.
	 * @param ident Nom de l'ident dont le type doit �tre empil�.
	 */
	public void empileTypeIdent(String ident){

		try{
			pileType.push(Yaka.tabIdent.getType(ident));
			//System.out.println(YakaTokenManager.identLu+" Type :"+Yaka.imageType[pileType.peek()]);
		}catch(TabIdentException e){
			Erreur.erreur(""+e.getMessage());
		}
	}

	/**
	 * Empile un op�rateur dans la pile des op�rateurs.
	 * @param op Op�rateur � empiler, si celui-ci est incorrect, un message d'erreur est affich�.
	 */
	public void empileOp(String op){
		if(!op.equals("+")
				&&!op.equals("-")
				&&!op.equals("*")
				&&!op.equals("/")
				&&!op.equals("OU")
				&&!op.equals("ET")
				&&!op.equals("<")
				&&!op.equals(">")
				&&!op.equals("=")
				&&!op.equals(">=")
				&&!op.equals("<=")
				&&!op.equals("<>")
				&&!op.equals("NON")
				&&!op.equals("_"))
			Erreur.erreur("Op�rateur "+op+" non reconnu");
		else{
			pileOp.push(op);
		}
	}

	/**
	 * Effectue la v�rification des types des diff�rents termes d'une expression puis, en cas de r�ussite, r�alise le calcul de cette expression.
	 */
	public void verifType(){
		boolean erreur=false;
		String op=pileOp.pop();

		if(op.equals("+") || op.equals("-") || op.equals("*") || op.equals("/")){
			int b=pileType.pop();
			int a=pileType.pop();
			if(a!=Yaka.T_ENTIER && a!=Yaka.T_ERREUR){
				Erreur.erreur("Erreur : premier argument de "+op+" trouv� de type "+Yaka.imageType[a]+", entier attendu");
				erreur=true;
				Yaka.yvm.correct=false;
			}
			if(b!=Yaka.T_ENTIER && b!=Yaka.T_ERREUR){
				Erreur.erreur("Erreur : deuxieme argument de "+op+" trouv� de type "+Yaka.imageType[a]+", entier attendu");
				erreur=true;
				Yaka.yvm.correct=false;
			}
			if(a==Yaka.T_ERREUR||b==Yaka.T_ERREUR) erreur=true;
			if(erreur)pileType.push(Yaka.T_ERREUR);
			else pileType.push(Yaka.T_ENTIER);
		}

		if(op.equals("=")){
			int b=pileType.pop();
			int a=pileType.pop();
			if(a!=b && b!=Yaka.T_ERREUR && a!=Yaka.T_ERREUR){
				Erreur.erreur("Erreur : premier argument de "+op+" trouv� de type "+Yaka.imageType[a]+", doit etre de meme type que le deuxieme arg trouve de type "+Yaka.imageType[b]);
				erreur=true;
				Yaka.yvm.correct=false;
			}
			if(a==Yaka.T_ERREUR||b==Yaka.T_ERREUR) erreur=true;
			if(erreur)pileType.push(Yaka.T_ERREUR);
			else pileType.push(Yaka.T_BOOL);
		}
		
		if(op.equals("<")||op.equals(">")||op.equals(">=")||op.equals("<=")||op.equals("<>")){
			int b=pileType.pop();
			int a=pileType.pop();
			if(a!=Yaka.T_ENTIER && a!=Yaka.T_ERREUR){
				Erreur.erreur("Erreur : premier argument de "+op+" trouv� de type "+Yaka.imageType[a]+", entier attendu");
				erreur=true;
				Yaka.yvm.correct=false;
			}
			if(b!=Yaka.T_ENTIER && b!=Yaka.T_ERREUR){
				Erreur.erreur("Erreur : deuxieme argument de "+op+" trouv� de type "+Yaka.imageType[a]+", entier attendu");
				erreur=true;
				Yaka.yvm.correct=false;
			}
			if(a==Yaka.T_ERREUR||b==Yaka.T_ERREUR) erreur=true;
			if(erreur)pileType.push(Yaka.T_ERREUR);
			else pileType.push(Yaka.T_BOOL);
		}

		if(op.equals("OU") || op.equals("ET")){
			int b=pileType.pop();
			int a=pileType.pop();
			if(a!=Yaka.T_BOOL && a!=Yaka.T_ERREUR){
				Erreur.erreur("Erreur : premier argument de "+op+" trouv� de type "+Yaka.imageType[a]+", booleen attendu");
				erreur=true;
				Yaka.yvm.correct=false;
			}
			if(b!=Yaka.T_BOOL && b!=Yaka.T_ERREUR){
				Erreur.erreur(" Erreur : deuxieme argument de "+op+" trouv� de type "+Yaka.imageType[a]+", booleen attendu");
				erreur=true;
				Yaka.yvm.correct=false;
			}
			if(a==Yaka.T_ERREUR||b==Yaka.T_ERREUR) erreur=true;
			if(erreur)pileType.push(Yaka.T_ERREUR);
			else pileType.push(Yaka.T_BOOL);
		}

		if(op.equals("NON")){
			int a=pileType.pop();
			if(a!=Yaka.T_BOOL && a!=Yaka.T_ERREUR){
				Erreur.erreur(" Erreur : argument de "+op+" trouv� de type "+Yaka.imageType[a]+", booleen attendu");
				erreur=true;
				Yaka.yvm.correct=false;
			}
			if(erreur)pileType.push(Yaka.T_ERREUR);
			else pileType.push(Yaka.T_BOOL);
		}

		if(op.equals("_")){ //correspond au - unaire
			int a=pileType.pop();
			if(a!=Yaka.T_ENTIER && a!=Yaka.T_ERREUR){
				Erreur.erreur(" Erreur : argument de - unaire trouv� de type "+Yaka.imageType[a]+", entier attendu");
				erreur=true;
				Yaka.yvm.correct=false;
			}
			if(erreur){
				pileType.push(Yaka.T_ERREUR);
				//System.out.println("Erreur trouvee");
			}
			else pileType.push(Yaka.T_ENTIER);
		}

		executeExpression(op);
	}

	/**
	 * Effectue le calcul d'une expression arithm�tique.
	 * @param op Op�rateur de l'expression arithm�tique.
	 */
	public void executeExpression(String op){
		if(op.equals("+")){
			Yaka.yvm.iadd();
		}
		else if(op.equals("-")){
			Yaka.yvm.isub();
		}
		else if(op.equals("/")){
			Yaka.yvm.idiv();
		}
		else if(op.equals("*")){
			Yaka.yvm.imul();
		}
		else if(op.equals("ET")){
			Yaka.yvm.iand();
		}
		else if(op.equals("OU")){
			Yaka.yvm.ior();
		}
		else if(op.equals("NON")){
			Yaka.yvm.idiv();
		}
		else if(op.equals("<")){
			Yaka.yvm.iinf();
		}
		else if(op.equals("<=")){
			Yaka.yvm.iinfegal();
		}
		else if(op.equals("=")){
			Yaka.yvm.iegal();
		}
		else if(op.equals(">=")){
			Yaka.yvm.isupegal();
		}
		else if(op.equals(">")){
			Yaka.yvm.isup();
		}
		else if(op.equals("_")){
			Yaka.yvm.ineg();
		}
		else if(op.equals("<>")){
			Yaka.yvm.idiff();
		}
		else
			Erreur.erreur(" Operateur non reconnu : "+op);
	}

	//--------------------Gestion des fonctions-----------------------

	/**
	 * Empile le nom de la fonction qui est appel�e dans le programme Yaka.
	 * Initialise le nombre de param�tres � 0 (cette valeur sera modifi�e par la suite en cas de passage de param�tres).
	 */
	void debutFonction(String nom){
		try {
			pileFun.push((IdFun)Yaka.tabIdent.chercheIdent(nom));
			nbArg.push(0);
		} catch (TabIdentException e) {
			System.err.println(e.getMessage());
		}
	}

	/**
	 * Ajoute un argument � la fonction appel�e.
	 */
	void addParam(){
		nbArg.push(nbArg.pop()+1);
	}

	/**
	 * V�rifie que le nombre de param�tres pass� � la fonction correspond avec celui de sa signature.
	 * Effectue aussi une v�rification des types de chaque param�tre par rapport � ceux de sa signature.
	 */
	void finAppelFonction(){
		IdFun f=pileFun.pop();
		int i=nbArg.pop();
		if(i==f.getArgs().size()){
			boolean erreur = false;
			ArrayList<Integer> args=f.getArgs();
			for(i--;i>0;i--){
				int type=pileType.pop();
				if(type==args.get(i)){}
				else if (type==Yaka.T_ERREUR){
					erreur=true;
				}
				else {
					Erreur.erreur(" Fonction "+f.getNom()+" : Erreur type param num "+
							i+" : "+Yaka.imageType[args.get(i)]+" attendu "+
							Yaka.imageType[type]+" trouv�");
					erreur=true;
				}
			}
			if(erreur) pileType.push(Yaka.T_ERREUR);
			else{
				pileType.push(f.getType());
			}
			Yaka.yvm.call(f.getNom());
		}else{
			
			Erreur.erreur(" Erreur fonction "+f.getNom()+" : "
					+f.getArgs().size()+" argument(s) attendu(s), "+i+" trouv�(s)");
			for(i--;i>0;i--){
				int type=pileType.pop();
			}
			pileType.push(Yaka.T_ERREUR);
		}
	}
	
	public void verifRetour(){
		try {
			int retour=Yaka.tabIdent.getType(YakaTokenManager.fonctionLue);
			if(YakaTokenManager.fonctionLue.equals("main")){
				Erreur.erreur("Erreur : pas de retourne dans le prog principal");
			}
			else if(pileType.peek()!=retour){
				//System.out.println("pas youpi");
				Erreur.erreur("Type de retour incorrect");
			}
		} catch (TabIdentException e) {
			Erreur.erreur("erreur dans verif retour:"+e.getMessage());
		}
	}

}
